Перейти к основному содержимому

Простые приложения на C++

Разработчику Архитектору

Простые приложения на C++

C++ сочетает в себе возможности низкоуровневого управления памятью с высокоуровневыми абстракциями стандартной библиотеки. Это позволяет создавать эффективные утилиты для работы с файлами, сетью и системой, сохраняя при этом высокую производительность. В данном разделе рассматриваются практические примеры программ, демонстрирующие ключевые аспекты языка: работу со строками, файловой системой, многопоточностью, сетевыми протоколами и сериализацией данных.

Все примеры используют современный стандарт C++17 или новее, что обеспечивает доступ к удобным контейнерам, умным указателям и алгоритмам сортировки.


Генератор паролей

Этот пример демонстрирует работу со строками, генерацию случайных чисел и использование библиотечных функций для манипуляции массивами символов.

Описание задачи

Программа принимает длину пароля и набор разрешенных символов (цифры, буквы верхнего и нижнего регистра, спецсимволы). Результат выводится в консоль.

Код программы

#include <iostream>
#include <string>
#include <vector>
#include <random>
#include <chrono>

class PasswordGenerator {
private:
std::string characters;
std::mt19937 generator;

public:
PasswordGenerator() {
// Инициализация генератора случайных чисел на основе текущего времени
auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
generator.seed(static_cast<unsigned long>(seed));

characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
}

std::string generate(size_t length) {
if (length == 0) return "";

std::uniform_int_distribution<size_t> distribution(0, characters.size() - 1);
std::string password;
password.reserve(length);

for (size_t i = 0; i < length; ++i) {
password += characters[distribution(generator)];
}

return password;
}
};

int main() {
PasswordGenerator gen;
size_t length = 16; // Стандартная длина пароля

std::cout << "Сгенерированный пароль: " << gen.generate(length) << std::endl;

return 0;
}

Разбор ключевых элементов

  • std::mt19937: Псевдослучайный генератор чисел, обеспечивающий равномерное распределение значений.
  • std::uniform_int_distribution: Распределяет целые числа в заданном диапазоне, исключая смещения.
  • std::string::reserve: Заранее выделяет память под строку, что повышает производительность при многократном добавлении символов.
  • Инициализация семени: Использование high_resolution_clock гарантирует уникальность последовательности при каждом запуске.

Сортировщик текстового файла

Пример показывает чтение содержимого файла, обработку строк, сортировку по алфавиту и запись результата обратно в файл или новый файл.

Описание задачи

Программа считывает все строки из входного файла, сортирует их лексикографически и сохраняет результат в выходной файл.

Код программы

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <algorithm>

class FileSorter {
public:
static bool sortFile(const std::string& inputPath, const std::string& outputPath) {
std::ifstream inputFile(inputPath);
if (!inputFile.is_open()) {
std::cerr << "Ошибка открытия входного файла: " << inputPath << std::endl;
return false;
}

std::vector<std::string> lines;
std::string line;

while (std::getline(inputFile, line)) {
lines.push_back(line);
}
inputFile.close();

std::sort(lines.begin(), lines.end());

std::ofstream outputFile(outputPath);
if (!outputFile.is_open()) {
std::cerr << "Ошибка открытия выходного файла: " << outputPath << std::endl;
return false;
}

for (const auto& l : lines) {
outputFile << l << "\n";
}
outputFile.close();

std::cout << "Файл успешно отсортирован: " << outputPath << std::endl;
return true;
}
};

int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Использование: " << argv[0] << " <входной_файл> <выходной_файл>" << std::endl;
return 1;
}

FileSorter::sortFile(argv[1], argv[2]);
return 0;
}

Разбор ключевых элементов

  • std::getline: Чтение строки до символа переноса строки, корректно обрабатывает файлы с разными типами переносов.
  • std::vector: Динамический массив для хранения строк произвольного размера.
  • std::sort: Алгоритм быстрой сортировки из STL, работающий за O(N log N).
  • Обработка ошибок: Проверка состояния потоков ввода/вывода перед началом операций.

Консольный калькулятор

Базовая программа для выполнения арифметических операций с использованием условных конструкций и обработки пользовательского ввода.

Описание задачи

Пользователь вводит два числа и знак операции (+, -, *, /). Программа выводит результат или сообщение об ошибке.

Код программы

#include <iostream>
#include <limits>

double calculate(double a, double b, char op) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/':
if (b == 0) throw std::runtime_error("Деление на ноль");
return a / b;
default: throw std::invalid_argument("Неизвестная операция");
}
}

int main() {
double num1, num2;
char operation;

std::cout << "Введите первое число: ";
std::cin >> num1;

std::cout << "Введите операцию (+, -, *, /): ";
std::cin >> operation;

std::cout << "Введите второе число: ";
std::cin >> num2;

try {
double result = calculate(num1, num2, operation);
std::cout << "Результат: " << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "Ошибка: " << e.what() << std::endl;
return 1;
}

return 0;
}

Разбор ключевых элементов

  • switch-case: Эффективная структура для выбора действия по значению переменной.
  • Исключения (try-catch): Обработка некорректных операций (деление на ноль, неверный символ).
  • Типы данных: Использование double позволяет работать с дробными числами.

Трекер задач в JSON

Пример сериализации и десериализации структуры данных в формат JSON с сохранением в файл. Для реализации используется библиотека nlohmann/json, которая является стандартом де-факто для работы с JSON в C++.

Описание задачи

Программа хранит список задач (ID, описание, статус) в JSON-файле. Поддерживается добавление новых задач и вывод текущего списка.

Код программы

Примечание: Для компиляции требуется подключить библиотеку nlohmann/json.

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <json/json.h> // Условное имя заголовка для nlohmann/json

struct Task {
int id;
std::string description;
bool completed;
};

class TaskTracker {
private:
std::string filePath;
std::vector<Task> tasks;

void load() {
std::ifstream file(filePath);
if (!file.is_open()) {
return; // Файл не существует, создаем пустой список
}

Json::Value root;
file >> root;
file.close();

tasks.clear();
for (const auto& taskVal : root) {
Task t;
t.id = taskVal["id"].asInt();
t.description = taskVal["description"].asString();
t.completed = taskVal["completed"].asBool();
tasks.push_back(t);
}
}

void save() {
std::ofstream file(filePath);
Json::Value root(Json::arrayValue);

for (const auto& t : tasks) {
Json::Value taskObj;
taskObj["id"] = t.id;
taskObj["description"] = t.description;
taskObj["completed"] = t.completed;
root.append(taskObj);
}

file << root;
file.close();
}

public:
TaskTracker(const std::string& path) : filePath(path) {
load();
}

void addTask(const std::string& desc) {
int newId = tasks.empty() ? 1 : tasks.back().id + 1;
tasks.push_back({newId, desc, false});
save();
}

void showTasks() {
std::cout << "--- Список задач ---" << std::endl;
for (const auto& t : tasks) {
std::cout << "[" << t.id << "] " << t.description
<< " (" << (t.completed ? "Выполнено" : "Ожидает") << ")" << std::endl;
}
}
};

int main() {
TaskTracker tracker("tasks.json");

tracker.addTask("Купить продукты");
tracker.addTask("Написать отчет");

tracker.showTasks();

return 0;
}

Разбор ключевых элементов

  • Структура данных: struct Task определяет схему записи.
  • JSON-парсинг: Библиотека автоматически преобразует C++ объекты в структуру JSON и обратно.
  • Управление состоянием: Методы load и save обеспечивают постоянство данных между запусками программы.

Простой HTTP-сервер и клиент

C++ не имеет встроенной поддержки HTTP в стандартной библиотеке, поэтому для сетевых операций используются сокеты. Ниже представлен минимальный сервер, принимающий запросы, и клиент, отправляющий их.

Описание задачи

Сервер слушает порт 8080 и возвращает текст "Hello, World!" на любой GET-запрос. Клиент отправляет этот запрос и выводит ответ.

Код сервера

#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>

#define PORT 8080

void handleClient(int clientSocket) {
char buffer[1024];
ssize_t bytesRead = read(clientSocket, buffer, sizeof(buffer) - 1);

if (bytesRead > 0) {
buffer[bytesRead] = '\0';

std::string response = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n"
"\r\n"
"Hello from C++ Server!";

write(clientSocket, response.c_str(), response.length());
}

close(clientSocket);
}

int main() {
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

bind(serverSocket, (struct sockaddr*)&address, sizeof(address));
listen(serverSocket, 3);

std::cout << "Сервер запущен на порту " << PORT << std::endl;

while (true) {
struct sockaddr_in clientAddress;
socklen_t clientLen = sizeof(clientAddress);
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientLen);

if (clientSocket < 0) continue;

handleClient(clientSocket);
}

close(serverSocket);
return 0;
}

Код клиента

#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr));

std::string request = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
send(sock, request.c_str(), request.length(), 0);

char buffer[1024];
ssize_t bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0);
buffer[bytesReceived] = '\0';

std::cout << "Ответ сервера:\n" << buffer << std::endl;

close(sock);
return 0;
}

Разбор ключевых элементов

  • Сокеты (socket, bind, listen, accept): Базовый API Unix/Linux для сетевого взаимодействия.
  • Текстовый протокол: Формирование HTTP-заголовков вручную через конкатенацию строк.
  • Асинхронность: Сервер работает в цикле, обрабатывая клиентов последовательно (для демонстрации).

Отправитель HTTP-запросов

Утилита для отправки произвольных HTTP-запросов с возможностью указания заголовков и тела запроса.

Описание задачи

Программа принимает URL, метод запроса (GET, POST), заголовки и тело, отправляет данные и выводит ответ.

Код программы

#include <iostream>
#include <string>
#include <sstream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

class HttpClient {
public:
static std::string sendRequest(const std::string& host, int port,
const std::string& method, const std::string& path,
const std::string& body = "") {
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
inet_pton(AF_INET, host.c_str(), &serverAddr.sin_addr);

if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
close(sock);
return "Ошибка подключения";
}

std::ostringstream requestStream;
requestStream << method << " " << path << " HTTP/1.1\r\n";
requestStream << "Host: " << host << "\r\n";
requestStream << "Content-Length: " << body.length() << "\r\n";
requestStream << "Content-Type: application/json\r\n";
requestStream << "\r\n";
requestStream << body;

std::string request = requestStream.str();
send(sock, request.c_str(), request.length(), 0);

char buffer[4096];
std::string response;
ssize_t bytesRead;

while ((bytesRead = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0) {
buffer[bytesRead] = '\0';
response += buffer;
}

close(sock);
return response;
}
};

int main() {
std::string body = "{\"message\": \"Hello from C++\"}";
std::string response = HttpClient::sendRequest("httpbin.org", 80, "POST", "/post", body);

std::cout << "Ответ:\n" << response << std::endl;
return 0;
}

Утилита для сканирования директорий

Программа рекурсивно обходит папку и выводит список всех файлов и подпапок с указанием их размеров.

Описание задачи

Пользователь указывает путь к директории. Программа выводит дерево файлов с размерами.

Код программы

#include <iostream>
#include <filesystem>
#include <string>

namespace fs = std::filesystem;

void scanDirectory(const fs::path& path, int indent = 0) {
for (const auto& entry : fs::directory_iterator(path)) {
std::string prefix(indent, ' ');
if (entry.is_directory()) {
std::cout << prefix << "[DIR] " << entry.path().filename() << std::endl;
scanDirectory(entry.path(), indent + 2);
} else {
auto size = entry.file_size();
std::cout << prefix << "[FILE] " << entry.path().filename()
<< " (" << size << " байт)" << std::endl;
}
}
}

int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Использование: " << argv[0] << " <путь_к_директории>" << std::endl;
return 1;
}

fs::path targetPath(argv[1]);
if (!fs::exists(targetPath) || !fs::is_directory(targetPath)) {
std::cerr << "Указанный путь не существует или не является директорией." << std::endl;
return 1;
}

scanDirectory(targetPath);
return 0;
}

Разбор ключевых элементов

  • std::filesystem: Современная библиотека для работы с файловой системой, введенная в C++17.
  • Рекурсия: Функция вызывает сама себя для обработки вложенных директорий.
  • Итераторы: directory_iterator позволяет проходить по элементам каталога без ручного управления указателями.

Скрипт для создания резервного копирования файлов

Программа копирует файлы из исходной папки в целевую, сохраняя структуру директорий и временные метки.

Описание задачи

Создает полную копию указанной папки в новое место назначения.

Код программы

#include <iostream>
#include <filesystem>
#include <system_error>

namespace fs = std::filesystem;

bool copyDirectoryWithProgress(const fs::path& source, const fs::path& dest) {
if (!fs::exists(source)) {
std::cerr << "Источник не найден: " << source << std::endl;
return false;
}

if (!fs::exists(dest.parent_path())) {
fs::create_directories(dest.parent_path());
}

std::error_code ec;
fs::copy(source, dest, fs::copy_options::recursive | fs::copy_options::overwrite_if_newer, ec);

if (ec) {
std::cerr << "Ошибка копирования: " << ec.message() << std::endl;
return false;
}

std::cout << "Резервное копирование завершено: " << dest << std::endl;
return true;
}

int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Использование: " << argv[0] << " <исходная_папка> <целевая_папка>" << std::endl;
return 1;
}

copyDirectoryWithProgress(argv[1], argv[2]);
return 0;
}

Мониторинг дискового пространства

Утилита выводит информацию о свободном и занятом месте на дисках системы.

Описание задачи

Отображает объем диска, свободное место и процент использования.

Код программы

#include <iostream>
#include <filesystem>
#include <iomanip>

namespace fs = std::filesystem;

void printDiskUsage(const fs::path& root) {
auto total = fs::space(root).total_bytes;
auto free = fs::space(root).free_bytes;
auto used = total - free;

double percentUsed = (static_cast<double>(used) / total) * 100.0;

std::cout << "Диск: " << root << std::endl;
std::cout << "Всего: " << total << " байт" << std::endl;
std::cout << "Занято: " << used << " байт (" << std::fixed << std::setprecision(2) << percentUsed << "%)" << std::endl;
std::cout << "Свободно: " << free << " байт" << std::endl;
std::cout << "---------------------------" << std::endl;
}

int main() {
fs::path root("/"); // Для Linux/macOS используйте "/" или конкретный том

#ifdef _WIN32
root = "C:\\"; // Для Windows
#endif

printDiskUsage(root);
return 0;
}

Парсер URL и проверка доступности ресурса

Программа анализирует URL, извлекает компоненты (протокол, хост, порт, путь) и проверяет доступность ресурса через HTTP.

Описание задачи

Пользователь вводит URL. Программа выводит его составные части и статус доступа (код ответа HTTP).

Код программы

#include <iostream>
#include <string>
#include <regex>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

struct ParsedUrl {
std::string scheme;
std::string host;
int port;
std::string path;
};

ParsedUrl parseUrl(const std::string& url) {
ParsedUrl result;
result.port = 80; // По умолчанию HTTP
result.scheme = "http";
result.path = "/";

// Простая регулярка для разбора
std::regex re(R"(^(\w+)://([^:/]+)(?::(\d+))?(/.*)?$)");
std::smatch matches;

if (std::regex_match(url, matches, re)) {
if (matches.size() >= 2) result.scheme = matches[1].str();
if (matches.size() >= 3) result.host = matches[2].str();
if (matches.size() >= 4 && !matches[3].str().empty()) {
result.port = std::stoi(matches[3].str());
}
if (matches.size() >= 5 && !matches[4].str().empty()) {
result.path = matches[4].str();
}
}
return result;
}

bool checkAvailability(const std::string& host, int port, const std::string& path) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
inet_pton(AF_INET, host.c_str(), &serverAddr.sin_addr);

if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
close(sock);
return false;
}

std::string request = "GET " + path + " HTTP/1.1\r\nHost: " + host + "\r\n\r\n";
send(sock, request.c_str(), request.length(), 0);

char buffer[1024];
ssize_t bytesRead = recv(sock, buffer, sizeof(buffer) - 1, 0);
buffer[bytesRead] = '\0';

close(sock);

// Извлекаем код ответа (первые 3 цифры после "HTTP/")
std::string response(buffer);
size_t pos = response.find(" ");
if (pos != std::string::npos) {
std::string codeStr = response.substr(pos + 1, 3);
int code = std::stoi(codeStr);
return (code >= 200 && code < 400);
}
return false;
}

int main() {
std::string url = "https://example.com/docs/api";

ParsedUrl parsed = parseUrl(url);

std::cout << "URL: " << url << std::endl;
std::cout << "Протокол: " << parsed.scheme << std::endl;
std::cout << "Хост: " << parsed.host << std::endl;
std::cout << "Порт: " << parsed.port << std::endl;
std::cout << "Путь: " << parsed.path << std::endl;

bool available = checkAvailability(parsed.host, parsed.port, parsed.path);
std::cout << "Доступен: " << (available ? "Да" : "Нет") << std::endl;

return 0;
}

Конвертер форматов дат

Утилита преобразует дату из одного формата в другой (например, из строки "DD.MM.YYYY" в "YYYY-MM-DD").

Описание задачи

Программа принимает дату в формате пользователя и конвертирует её в ISO 8601 формат.

Код программы

#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>

struct DateParts {
int day, month, year;
};

DateParts parseDate(const std::string& dateStr, char delimiter) {
std::stringstream ss(dateStr);
DateParts parts;
char sep;
ss >> parts.day >> sep >> parts.month >> sep >> parts.year;
return parts;
}

std::string convertToISO(DateParts parts) {
std::ostringstream oss;
oss << parts.year << "-"
<< std::setw(2) << std::setfill('0') << parts.month << "-"
<< std::setw(2) << std::setfill('0') << parts.day;
return oss.str();
}

int main() {
std::string input = "15.12.2023";

DateParts parts = parseDate(input, '.');
std::string isoFormat = convertToISO(parts);

std::cout << "Ввод: " << input << std::endl;
std::cout << "Вывод: " << isoFormat << std::endl;

return 0;
}

Утилита для просмотра запущенных процессов

Программа выводит список активных процессов с их идентификаторами (PID) и именами.

Описание задачи

Отображает текущие процессы, работающие в системе.

Код программы

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>

#ifdef _WIN32
#include <tlhelp32.h>
#else
#include <dirent.h>
#include <unistd.h>
#endif

class ProcessViewer {
public:
static void listProcesses() {
#ifdef _WIN32
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return;

PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);

if (Process32First(hSnapshot, &pe32)) {
do {
std::cout << "PID: " << pe32.th32ProcessID
<< " | Name: " << pe32.szExeFile << std::endl;
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
#else
DIR* dir;
struct dirent* ent;
if ((dir = opendir("/proc")) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (isdigit(ent->d_name[0])) {
std::string pid = ent->d_name;
std::string statPath = "/proc/" + pid + "/stat";
std::ifstream statFile(statPath);

if (statFile.is_open()) {
std::string name;
statFile >> name; // PID
statFile >> name; // Comm (имя процесса в скобках)

// Удаляем скобки из имени
name.erase(std::remove(name.begin(), name.end(), '('), name.end());
name.erase(std::remove(name.begin(), name.end(), ')'), name.end());

std::cout << "PID: " << pid << " | Name: " << name << std::endl;
statFile.close();
}
}
}
closedir(dir);
}
#endif
}
};

int main() {
std::cout << "Список запущенных процессов:" << std::endl;
ProcessViewer::listProcesses();
return 0;
}

Характерный пример именно для C++: Управление памятью и RAII

Одной из отличительных черт C++ является управление ресурсами через механизм RAII (Resource Acquisition Is Initialization). Это позволяет автоматизировать освобождение памяти и других ресурсов, предотвращая утечки.

Ниже приведен пример класса, который управляет динамическим массивом и используетRAII для безопасного освобождения памяти.

Код программы

#include <iostream>
#include <memory>

class ManagedArray {
private:
int* data;
size_t size;

public:
ManagedArray(size_t s) : size(s) {
std::cout << "Выделение памяти под массив размером " << size << std::endl;
data = new int[size];
for (size_t i = 0; i < size; ++i) {
data[i] = static_cast<int>(i);
}
}

~ManagedArray() {
std::cout << "Освобождение памяти массива" << std::endl;
delete[] data;
data = nullptr;
}

// Запрет копирования (чтобы избежать двойного удаления)
ManagedArray(const ManagedArray&) = delete;
ManagedArray& operator=(const ManagedArray&) = delete;

// Разрешение перемещения
ManagedArray(ManagedArray&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}

ManagedArray& operator=(ManagedArray&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}

void print() const {
std::cout << "Массив: ";
for (size_t i = 0; i < size; ++i) {
std::cout << data[i] << " ";
}
std::cout << std::endl;
}
};

// Пример использования умного указателя (std::unique_ptr)
void useSmartPointer() {
std::unique_ptr<ManagedArray> ptr(new ManagedArray(5));
ptr->print();
// Память освободится автоматически при выходе из области видимости
}

int main() {
{
ManagedArray arr(3);
arr.print();
// Деструктор вызывается здесь
}

useSmartPointer();
// Деструктор вызывается здесь

return 0;
}

Разбор ключевых элементов

  • Конструктор и деструктор: Гарантируют выделение и освобождение ресурсов в строго определенном порядке.
  • Запрет копирования: Предотвращает создание двух объектов, указывающих на одну область памяти.
  • Перемещение (Move Semantics): Позволяет эффективно передавать владение объектом без копирования данных.
  • std::unique_ptr: Автоматизирует управление памятью, используя принцип RAII.

Освоение главы0%